﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace ZoambieGaem {
    class ZoambieLogic {

        private long lastTick = 0;
        
        

        public void update(Master m) {
            if (m != null && m.zoambieList != null) {
                createZoambie(m);
                if (m.frameDrawn)
                    updateZoambie(m);
            }
        }


        private void zoambieDeath(Master m, Zoambie z)
        {
            z.death = true;
            m.deadzoambieList.AddLast(z);
            m.zoambieList.Remove(z);
        }


        //Creates a new zoambie, if one second has passed
        private void createZoambie(Master m) {
            if (System.Environment.TickCount - lastTick >= 1000 && m.zoambieList.Count() < 500) {
                lastTick = System.Environment.TickCount;
                Point point = randomZombieLocation(m);
                Zoambie z = new Zoambie(
                    m.zoambie,
                    (int)(((point.x * m.blockSize) + (m.blockSize / 2))),
                    (int)(((point.y * m.blockSize) + (m.blockSize / 2))),
                    m);
                m.zoambieList.AddFirst(z);
            }
        }




        //Creates a random starting location for zombies
        private Point randomZombieLocation(Master m) {
            Point point = new Point(5, 5);
            int side = 0;
            int x = 0;
            int y = 0;

            //Randomly choose the side of the screen where the zombie is going to spawn

            Random randNum = new Random();
            double random = randNum.NextDouble();

            if (random <= 0.25) {
                side = 1;
            }
            else if (random > 0.25 && random <= 0.50) {
                side = 2;
            }
            else if (random > 0.50 && random <= 0.75) {
                side = 3;
            }
            else if (random > 0.75) {
                side = 4;
            }

            //Randomly choose the spot where the zombie is going to spawn on the specific side

            random = randNum.NextDouble();
            if (side == 1) {
                x = Maps.width - 1;
                y = (int)(Maps.height * random);
                point = new Point(x, y);
            }
            else if (side == 2) {
                x = 0;
                y = (int)(Maps.height * random);
                point = new Point(x, y);
            }
            else if (side == 3) {
                y = 0;
                x = (int)(Maps.width * random);
                point = new Point(x, y);
            }
            else if (side == 4) {
                y = Maps.height - 1;
                x = (int)(Maps.width * random);
                point = new Point(x, y);
            }

            return point;

        }





        private Boolean collisionDetectZombieWall(Master m, Zoambie z, Vector2 coordCandidate) {

            Boolean collision = false;
            int zombienextX;
            int zombienextY;
            //lisää blocksizen puolet

            zombienextX = (int)((coordCandidate.X / m.blockSize));
            zombienextY = (int)((coordCandidate.Y / m.blockSize));

            //Int cast, because Rectangle doesnt accept Vector2
            int zombieCurrentX = (int)(z.worldPosition.X);
            int zombieCurrentY = (int)(z.worldPosition.Y);

            Vector2 lefttop = new Vector2(zombieCurrentX - (z.width / 2), zombieCurrentY + (z.height / 2));
            Vector2 righttop = new Vector2(zombieCurrentX + (z.width / 2), zombieCurrentY + (z.height / 2));
            Vector2 leftbottom = new Vector2(zombieCurrentX - (z.width / 2), zombieCurrentY - (z.height / 2));
            Vector2 rightbottom = new Vector2(zombieCurrentX + (z.width / 2), zombieCurrentY - (z.height / 2));

            try {
                if (Maps.map[(int)lefttop.Y / m.blockSize][(int)lefttop.X / m.blockSize] == 1 ||
                    Maps.map[(int)righttop.Y / m.blockSize][(int)righttop.X / m.blockSize] == 1 ||
                    Maps.map[(int)leftbottom.Y / m.blockSize][(int)leftbottom.X / m.blockSize] == 1 ||
                    Maps.map[(int)rightbottom.Y / m.blockSize][(int)rightbottom.X / m.blockSize] == 1) {

                    collision = true;
                    z.lineOfSight = false;
                }
                else
                    collision = false;
            }
            catch (Exception e) {
                String turha = " " + e.ToString();
                Console.WriteLine("WALL DETECT FAIL" + turha);
            }
            return collision;
        }





        private void updateZombieAngle(Zoambie z) {
            float speed = 0f;

            float first = MathHelper.WrapAngle(z.wantedAngle - z.angle);
            float second = MathHelper.WrapAngle(z.angle - z.wantedAngle);
            if (first < second) {
                speed = 0.05f * first;
                z.angle += speed;
            }
            else {
                speed = 0.05f * second;
                z.angle -= speed;
            }
        }




        private void pushZombie(Master m, Zoambie z, String type) {
            if (type == "wall") {
                int zx = (int)(z.worldPosition.X / m.blockSize);
                int zy = (int)(z.worldPosition.Y / m.blockSize);
                float zyy = ((z.worldPosition.Y / (float)m.blockSize)) - zy;
                float zxx = ((z.worldPosition.X / (float)m.blockSize)) - zx;

                if (zx < Maps.width && zx >= 0 && zy < Maps.height && zy >= 0) {

                    try {
                        if (zxx < 0.5) {
                            if (zx - 1 >= 0 && Maps.map[zy][zx - 1] == 1)
                                z.worldPosition = new Vector2(z.worldPosition.X + 1, z.worldPosition.Y);
                        }
                        else if (zxx > 0.5) {
                            if (zx + 1 < Maps.width && Maps.map[zy][zx + 1] == 1)
                                z.worldPosition = new Vector2(z.worldPosition.X - 1, z.worldPosition.Y);
                        }
                        if (zyy < 0.5) {
                            if (zy - 1 >= 0 && Maps.map[zy - 1][zx] == 1)
                                z.worldPosition = new Vector2(z.worldPosition.X, z.worldPosition.Y + 1);
                        }
                        else if (zyy > 0.5) {
                            if (zy + 1 < Maps.height && Maps.map[zy + 1][zx] == 1)
                                z.worldPosition = new Vector2(z.worldPosition.X, z.worldPosition.Y - 1);

                        }
                    }
                    catch (Exception e) {
                        Console.WriteLine("ZOMBIE COLLISION DETECT FAILURE " + e);
                    }
                }

            }
        }


        private void zoambieNextStep(Master m, Zoambie z) {
            if (m.frameDrawn) {
                double angle = (float)(Math.Atan2(z.zombieDirection.Y, z.zombieDirection.X));
                z.wantedAngle = (float)angle;
                updateZombieAngle(z);

                Vector2 coordCandidate = new Vector2(z.worldPosition.X + (float)(Math.Cos(z.angle) * z.speed),
                                                     z.worldPosition.Y + (float)(Math.Sin(z.angle) * z.speed));


                Vector2 coordCandidate2 = new Vector2(z.worldPosition.X + (float)(Math.Cos(z.angle) * (z.speed + (z.width * 3))),
                                                      z.worldPosition.Y + (float)(Math.Sin(z.angle) * (z.speed + (z.width * 3))));

                z.worldPosition = new Vector2(coordCandidate.X, coordCandidate.Y);

                //Katsotaan osuuko zombi pelaajaan
                collisionDetect(m, z);

                //pushissa tarkistetaan törmääkö zombie seinään
                pushZombie(m, z, "wall");
            }
        }


        private Boolean lineOfSight(Master m, Zoambie z) {
            Node goal = new Node(
                (int)m.player.worldPosition.X / m.blockSize,
                (int)m.player.worldPosition.Y / m.blockSize);
            Node start = new Node(
                (int)z.worldPosition.X / m.blockSize,
                (int)z.worldPosition.Y / m.blockSize);
            Boolean done = false;
            Node current;
            int fastest = 99999;
            int newHeur = 0;
            Node best = start;
            int round = 1;
            while (!done) {
                done = true;
                current = best;
                if (current.x == goal.x && current.y == goal.y) {
                    //Console.WriteLine("LINE OF SIGHT");
                    return true;
                }

                if ((current.x >= 0 && current.x < Maps.width && current.y >= 0 && current.y < Maps.height)) {
                    if (Maps.map[(int)current.y][(int)current.x] == 1) {
                        //Console.WriteLine("NO SIGHT");
                        return false;
                    }
                }

                LinkedList<Node> newPoints = new LinkedList<Node>();
                newPoints.AddFirst(new Node(current.x - 1, current.y));
                newPoints.AddFirst(new Node(current.x + 1, current.y));
                newPoints.AddFirst(new Node(current.x, current.y - 1));
                newPoints.AddFirst(new Node(current.x, current.y + 1));

                foreach (Node next in newPoints) {
                    round++;
                    if ((next.x >= 0 && next.x < Maps.width && next.y >= 0 && next.y < Maps.height)) {
                        if (Maps.map[(int)next.y][(int)next.x] != 1) {
                            newHeur = heurestics(next, goal);
                            if (newHeur < fastest) {
                                done = false;
                                fastest = newHeur;
                                best = next;
                            }

                        }
                    }
                }
            }

            //Console.WriteLine("NO SIGHT");
            return false;
        }


        //Movement updates for zoambies
        private void updateZoambie(Master m) {

            for (int i = m.zoambieList.Count() - 1; i >= 0; i--) {
                Zoambie z = m.zoambieList.ElementAt(i);

                if (z.death == false) {
                    collisionDetectProjectile(m, z);
                    if (z.health > 0) {

                        z.aStarUpdate++;
                        if (z.aStarUpdate > 30) {
                            z.lineOfSight = lineOfSight(m, z);
                            z.aStarUpdate = 0;
                        }

                        if (z.lineOfSight == true) {
                            z.path = null;
                            z.zombieDirection = new Vector2(
                                m.player.worldPosition.X - z.worldPosition.X,
                                m.player.worldPosition.Y - z.worldPosition.Y);
                        }

                        else if (z.lineOfSight == false) {
                            int zx = (int)(z.worldPosition.X / m.blockSize);
                            int zy = (int)(z.worldPosition.Y / m.blockSize);

                            int bestX = 0;
                            int bestY = 0;
                            int bestG = 999999;
                            for (int y = -1; y < 2; y++) {
                                for (int x = -1; x < 2; x++) {
                                    if (zx + x >= 0 &&
                                        zx + x < Maps.width &&
                                        zy + y >= 0 &&
                                        zy + y < Maps.height) {

                                        if (!hasCorners(zx, zy, zx + x, zy + y)) {
                                            if (m.routeMap[zy + y][zx + x] < bestG) {
                                                bestG = m.routeMap[zy + y][zx + x];
                                                bestX = zx + x;
                                                bestY = zy + y;
                                            }
                                        }
                                    }
                                }
                            }
                            z.zombieDirection = new Vector2(
                                (bestX * m.blockSize + (m.blockSize / 2)) - z.worldPosition.X,
                                (bestY * m.blockSize + (m.blockSize / 2)) - z.worldPosition.Y);
                        }

                        zoambieNextStep(m, z);
                    }
                    else
                        zoambieDeath(m, z);
                }
            }
        }





        // Player-Zoambie - collision detection
        private void collisionDetect(Master m, Zoambie z) {
            Boolean xOk = false;
            Boolean yOk = false;
            xOk = false;
            yOk = false;
            if (m.player.worldPosition != null && z.worldPosition != null && !m.player.gettingDamage) {
                if (z.worldPosition.X + (z.width / 2) >= m.player.worldPosition.X - (m.player.width / 2) &&
                    z.worldPosition.X - (z.width / 2) <= m.player.worldPosition.X + (m.player.width / 2))
                    xOk = true;
                if (z.worldPosition.Y + (z.height / 2) >= m.player.worldPosition.Y - (m.player.height / 2) &&
                    z.worldPosition.Y - (z.height / 2) <= m.player.worldPosition.Y + (m.player.height / 2))
                    yOk = true;

                if (xOk && yOk) {
                    m.player.gettingDamage = true;
                    m.player.damageAngle = z.angle;
                    m.player.damageSpeed = 5;
                    m.bloodList.AddFirst(new Blood(m, m.player.worldPosition, 0, z.angle));
                }
            }
        }

        // Projectile-Zoambie collision detection
        private Boolean collisionDetectProjectile(Master m, Zoambie z)
        {
            Boolean collision = false;

            int xCenter = (int)z.worldPosition.X;
            int yCenter = (int)z.worldPosition.Y;

            for (int i = m.projectileList.Count - 1; i >= 0; i--)
            {

                try
                {

                    Projectile p = m.projectileList.ElementAt(i);

                    //Alemmassa iffissä liikutetaan luotia ja katsotaan ettei se törmää seinään
                    if (p.update(m))
                    {

                        if (p.worldPosition.X > (z.worldPosition.X - z.width / 2) && p.worldPosition.X < (z.worldPosition.X + z.width / 2) &&
                            p.worldPosition.Y > (z.worldPosition.Y - z.height / 2) && p.worldPosition.Y < (z.worldPosition.Y + z.height / 2))
                        {

                            collision = true;
                            z.health -= p.damage;
                            m.bloodList.AddFirst(new Blood(m, z.worldPosition, 1, p.angle));
                            if (p.type != 2)
                            {
                                m.projectileList.Remove(p);
                            }
                            else
                            {
                                p.speed += Weapons.getSpeedModifier(p.type);
                                p.range += Weapons.getRangeModifier(p.type);
                                p.damage += Weapons.getDamageModifier(p.type);
                            }
                        }

                    }
                    else
                        m.projectileList.Remove(p);
                }
                catch (Exception e) 
                {
                    Console.WriteLine("PROJECTILE DETECT FAIL " + e);
                    return collision;
                }
            }
                return collision;
        }


        private int heurestics(Node start, Node goal) {
            double heur = 0;
            if (start.x > goal.x)
                heur += start.x - goal.x;
            else
                heur += goal.x - start.x;
            if (start.y > goal.y)
                heur += start.y - goal.y;
            else
                heur += goal.y - start.y;
            heur *= 10;
            return (int)heur;
        }



        private Boolean hasCorners(int sx, int sy, int gx, int gy) {
            Boolean skip = false;
            if (sx == gx || sy == gy)
                return false;
            else {
                try {
                    if (gx - 1 >= 0 &&
                        gx + 1 < Maps.width &&
                        gy - 1 >= 0 &&
                        gy + 1 < Maps.height &&
                        sx - 1 >= 0 &&
                        sx + 1 < Maps.width &&
                        sy - 1 >= 0 &&
                        sy + 1 < Maps.height) {


                        if (sx > gx && sy > gy) {
                            if (Maps.map[sy - 1][sx] == 1)
                                return true;
                            if (Maps.map[sy][sx - 1] == 1)
                                return true;
                        }
                        else if (sx > gx && sy <= gy) {
                            if (Maps.map[sy + 1][sx] == 1)
                                return true;
                            if (Maps.map[sy][sx - 1] == 1)
                                return true;
                        }
                        else if (sx <= gx && sy <= gy) {
                            if (Maps.map[sy + 1][sx] == 1)
                                return true;
                            if (Maps.map[sy][sx + 1] == 1)
                                return true;
                        }
                        else if (sx <= gx && sy > gy) {
                            if (Maps.map[sy - 1][sx] == 1)
                                return true;
                            if (Maps.map[sy][sx + 1] == 1)
                                return true;
                        }
                    }
                }
                catch (Exception e) {
                    String turha = " " + e.ToString();
                    Console.WriteLine("CORNER DETECT FAIL " + turha);
                }
            }
            return skip;
        }





    }
}
